package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.cache.CategoryCache;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.client.search.SearchClient;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.*;
import com.tianji.learning.domain.dto.QuestionFormDTO;
import com.tianji.learning.domain.po.InteractionQuestion;
import com.tianji.learning.domain.po.InteractionReply;
import com.tianji.learning.domain.query.QuestionAdminPageQuery;
import com.tianji.learning.domain.query.QuestionPageQuery;
import com.tianji.learning.domain.vo.QuestionAdminVO;
import com.tianji.learning.domain.vo.QuestionVO;
import com.tianji.learning.enums.QuestionStatus;
import com.tianji.learning.mapper.InteractionQuestionMapper;
import com.tianji.learning.service.IInteractionQuestionService;
import com.tianji.learning.service.IInteractionReplyService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 互动提问的问题表 服务实现类
 * </p>
 *
 * @author zr
 * @since 2024-07-16
 */
@Service
@RequiredArgsConstructor
public class InteractionQuestionServiceImpl extends ServiceImpl<InteractionQuestionMapper, InteractionQuestion> implements IInteractionQuestionService {

    private final IInteractionReplyService replyService;

    private final UserClient userClient;

    private final CourseClient courseClient;

    private final SearchClient searchClient;

    private final CatalogueClient catalogueClient;

    private final CategoryCache categoryCache;
    /**
     * 保存问题信息。
     * <p>
     * 该方法接收一个QuestionFormDTO对象作为参数，该对象包含了用户提交的问题的所有相关信息。
     *
     * @param questionDTO 包含问题信息的数据传输对象。这个对象应该包含了问题的所有必要属性，
     *                    如问题的标题、内容、提交者等信息。
     */
    @Override
    public void saveQuestion(QuestionFormDTO questionDTO) {
        // 1.获取当前登录的用户id
        Long userId = UserContext.getUser();
        // 2.数据封装
        InteractionQuestion question = BeanUtils.copyBean(questionDTO, InteractionQuestion.class);
        question.setUserId(userId);
        // 3.写入数据库
        save(question);
    }

    /**
     * 更新问题的信息。
     * <p>
     * 通过此方法，可以对数据库中已存在的问题进行修改。修改的内容由QuestionFormDTO对象携带，
     * 包括问题的各个属性，如问题描述、答案等。
     *
     * @param id          问题的唯一标识符，用于在数据库中定位到需要更新的问题。
     * @param questionDTO 包含更新后问题信息的数据传输对象，通过此对象的属性值来更新数据库中的问题。
     */
    @Override
    public void updateQuestion(Long id, QuestionFormDTO questionDTO) {
        if(StringUtils.isBlank(questionDTO.getTitle()) || StringUtils.isBlank(questionDTO.getDescription()) || questionDTO.getAnonymity()==null){
            throw new BadRequestException("请求参数不能为空");
        }
        // 1.查询当前问题
        InteractionQuestion q = getById(id);
        AssertUtils.isNotNull(q,"该问题不存在");
        // 2.获取当前登录用户
        Long userId = UserContext.getUser();
        // 3.判断是否是当前用户的问题
        AssertUtils.equals(q.getUserId(),userId,"无权修改他人的问题");
        // 4.修改问题
        InteractionQuestion question = BeanUtils.toBean(questionDTO, InteractionQuestion.class);
        question.setId(id);
        updateById(question);
    }

    /**
     * 根据指定的ID删除本账号的问题。
     * <p>
     * 此方法旨在通过提供的ID来删除数据库中对应的问题记录。
     *
     * @param id 要删除的实体的唯一标识符。这个参数是必需的，且不能为null，
     *           它被用于准确地定位到要删除的实体。
     */
    @Transactional
    @Override
    public void deleteById(Long id) {
        // 1.获取当前登录用户
        Long userId = UserContext.getUser();
        // 2.查询当前问题
        InteractionQuestion q = getById(id);
        if (q == null) {
            return;
        }
        // 3.判断是否是当前用户的问题
        AssertUtils.equals(userId,q.getUserId(),"无权删除他人的问题");
        // 4.删除问题
        removeById(id);
        // 5.删除答案
        replyService.remove(new LambdaQueryWrapper<InteractionReply>()
                .eq(InteractionReply::getQuestionId, q.getId()));
    }

    /**
     * 根据查询条件获取问题分页数据。
     * <p>
     * 该方法通过QuestionPageQuery参数来检索问题的分页信息。它封装了对问题数据的查询条件，
     * 并返回包含问题数据的分页DTO对象。
     *
     * @param query 查询条件对象，包含分页和过滤条件。
     * @return PageDTO<QuestionVO> 包含查询结果的分页数据传输对象。
     */
    @Override
    public PageDTO<QuestionVO> queryQuestionPage(QuestionPageQuery query) {
        // 1.校验courseId
        AssertUtils.isNotNull(query.getCourseId(), "课程id不能为空");
        // 2.获取用户
        Long userId = UserContext.getUser();
        // 3.分页查询
        Page<InteractionQuestion> page = lambdaQuery()
                //排除问题详情字段，主要原因是详情占用情况(2048字节)太大
                .select(InteractionQuestion.class, tableFieldInfo -> !tableFieldInfo.getProperty().equals(
                        "description"))
                .eq(query.getOnlyMine(), InteractionQuestion::getUserId, userId)
                .eq(InteractionQuestion::getCourseId, query.getCourseId())
                .eq(query.getSectionId() != null, InteractionQuestion::getSectionId, query.getSectionId())
                .eq(InteractionQuestion::getHidden, Boolean.FALSE)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<InteractionQuestion> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 4.根据id查询提问者和最近一次回答的信息
        Set<Long> userIds = new HashSet<>();
        Set<Long> answerIds = new HashSet<>();
        // 4.1.得到问题当中的提问者id和最近一次回答的id
        for (InteractionQuestion q : records) {
            // 该问题如果不是匿名才存放提问者id
            if(!q.getAnonymity()){
                userIds.add(q.getUserId());
            }
            // 该问题有回答者才存放回答id
            if(q.getLatestAnswerId() != null){
                answerIds.add(q.getLatestAnswerId());
            }
        }
        // 4.2.根据id查询最近一次回答
        Map<Long, InteractionReply> replyMap = new HashMap<>(answerIds.size());
        if(CollUtils.isNotEmpty(answerIds)){
            // 映射ids为回答map
            // 直接查询回答的话有可能该回答已经被隐藏了，所以只查出未隐藏的回答
            List<InteractionReply> replyList = replyService.lambdaQuery()
                    .eq(InteractionReply::getHidden, Boolean.FALSE)
                    .in(InteractionReply::getId, answerIds)
                    .list();
            // 把回答的回答者id拿出来，放到用户集合待查询个人信息
            for (InteractionReply reply : replyList) {
                if(!reply.getAnonymity()){
                    userIds.add(reply.getUserId());
                }
                replyMap.put(reply.getId(),reply);
            }
        }
        //5.批量远程查询用户信息->获取用户名称和头像(回答者和提问者)
        Map<Long, UserDTO> userMap = new HashMap<>(userIds.size());
        if(CollUtils.isNotEmpty(userIds)){
            userMap = userClient.queryUserByIds(userIds).stream().collect(Collectors.toMap(UserDTO::getId, c -> c));
        }
        // 6.封装VO
        List<QuestionVO> voList = new ArrayList<>(records.size());
        for (InteractionQuestion r : records) {
            // 6.1.将PO转为VO
            QuestionVO vo = BeanUtils.copyBean(r, QuestionVO.class);
            voList.add(vo);
            // 6.2.封装提问者信息
            if(!r.getAnonymity()){
                UserDTO userDTO = userMap.get(r.getUserId());
                if (userDTO != null) {
                    vo.setUserName(userDTO.getName());
                    vo.setUserIcon(userDTO.getIcon());
                }
            }
            // 6.3.封装最近一次回答的信息
            InteractionReply reply = replyMap.get(r.getLatestAnswerId());
            if (reply != null) {
                vo.setLatestReplyContent(reply.getContent());
                if(!reply.getAnonymity()){
                    UserDTO user = userMap.get(reply.getUserId());
                    if(user!=null){
                        vo.setLatestReplyUser(user.getName());
                    }
                }
            }
        }
        return PageDTO.of(page, voList);
    }

    /**
     * 根据问题ID查询问题详情。
     * <p>
     * 本方法通过调用数据库查询接口，使用问题的唯一标识ID来获取特定问题的详细信息。
     * 这些信息可能包括问题的标题、描述、创建时间等。该方法旨在为前端提供一个问题查询接口，
     * 以便用户可以通过问题ID来查看问题的详细内容。
     *
     * @param id 问题的唯一标识ID，用于在数据库中定位特定问题。
     * @return 返回查询到的问题详情，封装在QuestionVO对象中。如果未找到对应问题，则返回null。
     */
    @Override
    public QuestionVO queryQuestionById(Long id) {
        AssertUtils.isNotNull(id,"非法参数");
        // 1.根据id查询数据
        InteractionQuestion question = getById(id);
        // 2.数据校验
        if(question == null || question.getHidden()){
            // 没有数据或者是被隐藏了
            return null;
        }
        // 3.查询提问者信息
        UserDTO user = null;
        if(!question.getAnonymity()){
            user = userClient.queryUserById(question.getUserId());
        }
        // 4.封装VO
        QuestionVO vo = BeanUtils.copyBean(question, QuestionVO.class);
        if (user != null) {
            vo.setUserName(user.getName());
            vo.setUserIcon(user.getIcon());
        }
        return vo;
    }

    /**
     * 查询问题管理页面的分页信息。
     * <p>
     * 该方法通过QuestionAdminPageQuery查询条件来获取问题管理页面的分页数据。返回PageDTO<QuestionAdminVO>对象，
     * 包含分页信息和问题的详细管理视图（QuestionAdminVO）。
     *
     * @param query 查询条件对象，包含分页信息和过滤条件。
     * @return 返回一个PageDTO<QuestionAdminVO>对象，其中包含查询结果的分页信息和问题详情。
     */
    @Override
    public PageDTO<QuestionAdminVO> queryQuestionPageAdmin(QuestionAdminPageQuery query) {
        // 1.处理课程名称，得到课程id
        List<Long> courseIds = null;
        if (StringUtils.isNotBlank(query.getCourseName())) {
            courseIds = searchClient.queryCoursesIdByName(query.getCourseName());
            if (CollUtils.isEmpty(courseIds)) {
                return PageDTO.empty(0L, 0L);
            }
        }
        // 2.分页查询
        Integer status = query.getStatus();
        LocalDateTime begin = query.getBeginTime();
        LocalDateTime end = query.getEndTime();
        Page<InteractionQuestion> page = lambdaQuery()
                .in(courseIds != null, InteractionQuestion::getCourseId, courseIds)
                .eq(status != null, InteractionQuestion::getStatus, status)
                .gt(begin != null, InteractionQuestion::getCreateTime, begin)
                .lt(end != null, InteractionQuestion::getCreateTime, end)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<InteractionQuestion> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 3.准备VO需要的数据：用户数据、课程数据、章节数据
        Set<Long> userIds = new HashSet<>();
        Set<Long> cIds = new HashSet<>();
        Set<Long> cataIds = new HashSet<>();
        // 3.1.获取各种数据的id集合
        for (InteractionQuestion q : records) {
            userIds.add(q.getUserId());
            cIds.add(q.getCourseId());
            cataIds.add(q.getChapterId());
            cataIds.add(q.getSectionId());
        }
        // 3.2.根据id查询用户
        List<UserDTO> users = userClient.queryUserByIds(userIds);
        Map<Long, UserDTO> userMap = new HashMap<>(users.size());
        if (CollUtils.isNotEmpty(users)) {
            userMap = users.stream().collect(Collectors.toMap(UserDTO::getId, u -> u));
        }

        // 3.3.根据id查询课程
        List<CourseSimpleInfoDTO> cInfos = courseClient.getSimpleInfoList(cIds);
        Map<Long, CourseSimpleInfoDTO> cInfoMap = new HashMap<>(cInfos.size());
        if (CollUtils.isNotEmpty(cInfos)) {
            cInfoMap = cInfos.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
        }

        // 3.4.根据id查询章节
        List<CataSimpleInfoDTO> catas = catalogueClient.batchQueryCatalogue(cataIds);
        Map<Long, String> cataMap = new HashMap<>(catas.size());
        if (CollUtils.isNotEmpty(catas)) {
            cataMap = catas.stream()
                    .collect(Collectors.toMap(CataSimpleInfoDTO::getId, CataSimpleInfoDTO::getName));
        }
        // 4.封装VO
        List<QuestionAdminVO> voList = new ArrayList<>(records.size());
        for (InteractionQuestion q : records) {
            // 4.1.将PO转VO，属性拷贝
            QuestionAdminVO vo = BeanUtils.copyBean(q, QuestionAdminVO.class);
            voList.add(vo);
            // 4.2.用户信息
            UserDTO user = userMap.get(q.getUserId());
            if (user != null) {
                vo.setUserName(user.getName());
            }
            // 4.3.课程信息以及分类信息
            CourseSimpleInfoDTO cInfo = cInfoMap.get(q.getCourseId());
            if (cInfo != null) {
                vo.setCourseName(cInfo.getName());
                vo.setCategoryName(categoryCache.getCategoryNames(cInfo.getCategoryIds()));
            }
            // 4.4.章节信息
            vo.setChapterName(cataMap.getOrDefault(q.getChapterId(), ""));
            vo.setSectionName(cataMap.getOrDefault(q.getSectionId(), ""));
        }
        return PageDTO.of(page, voList);
    }

    /**
     * 根据给定的ID和隐藏状态来隐藏问题。
     * 此方法用于更新问题的隐藏状态，可以通过指定问题的ID和一个新的隐藏状态来实现。
     * 如果hidden为true，则将问题设置为隐藏；如果为false，则将问题设置为可见。
     *
     * @param id     问题的唯一标识符。用于确定要更新哪个问题的状态。
     * @param hidden 一个布尔值，指示问题的新隐藏状态。true表示隐藏，false表示可见。
     */
    @Override
    public void hiddenQuestion(Long id, Boolean hidden) {
        // 1.更新问题
        InteractionQuestion question = new InteractionQuestion();
        question.setId(id);
        question.setHidden(hidden);
        updateById(question);
    }

    /**
     * 根据问题ID查询问题的管理员视图对象。
     * <p>
     * 此方法用于管理员后台，通过问题的唯一标识符ID，从数据库中检索该问题的详细信息。
     * 返回的问题信息是专门为管理员设计的视图对象，包含比普通用户视图更多的管理相关字段。
     *
     * @param id 问题的唯一标识符，类型为长整型。
     * @return 返回管理员视图下的问题对象，如果找不到对应问题，则可能返回null。
     */
    @Override
    public QuestionAdminVO queryQuestionByIdAdmin(Long id) {
        // 1.根据id查询问题
        InteractionQuestion question = getById(id);
        if (question == null) {
            return null;
        }
        // 2.转PO为VO
        QuestionAdminVO vo = BeanUtils.copyBean(question, QuestionAdminVO.class);
        // 3.查询提问者信息
        UserDTO user = userClient.queryUserById(question.getUserId());
        if (user != null) {
            vo.setUserName(user.getName());
            vo.setUserIcon(user.getIcon());
        }
        // 4.查询课程信息
        CourseFullInfoDTO cInfo = courseClient.getCourseInfoById(
                question.getCourseId(), false, true);
        if (cInfo != null) {
            // 4.1.课程名称信息
            vo.setCourseName(cInfo.getName());
            // 4.2.分类信息
            vo.setCategoryName(categoryCache.getCategoryNames(cInfo.getCategoryIds()));
            // 4.3.教师信息
            List<Long> teacherIds = cInfo.getTeacherIds();
            List<UserDTO> teachers = userClient.queryUserByIds(teacherIds);
            if(CollUtils.isNotEmpty(teachers)) {
                vo.setTeacherName(teachers.stream()
                        .map(UserDTO::getName).collect(Collectors.joining("/")));
            }
        }
        // 5.查询章节信息
        List<CataSimpleInfoDTO> catas = catalogueClient.batchQueryCatalogue(
                List.of(question.getChapterId(), question.getSectionId()));
        Map<Long, String> cataMap = new HashMap<>(catas.size());
        if (CollUtils.isNotEmpty(catas)) {
            cataMap = catas.stream()
                    .collect(Collectors.toMap(CataSimpleInfoDTO::getId, CataSimpleInfoDTO::getName));
        }
        vo.setChapterName(cataMap.getOrDefault(question.getChapterId(), ""));
        vo.setSectionName(cataMap.getOrDefault(question.getSectionId(), ""));
        // 6.修改问题状态为已查看
        boolean update = lambdaUpdate()
                .eq(InteractionQuestion::getId, id)
                .set(InteractionQuestion::getStatus, QuestionStatus.CHECKED)
                .update();
        if(!update){
            throw new DbException("问题状态修改失败");
        }
        // 7.封装VO
        return vo;
    }
}